//
// ping.c
//
// Send ICMP echo request to network host
//
// Copyright (C) 2002 Michael Ringgaard. All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
// 
// 1. Redistributions of source code must retain the above copyright 
//    notice, this list of conditions and the following disclaimer.  
// 2. Redistributions in binary form must reproduce the above copyright
//    notice, this list of conditions and the following disclaimer in the
//    documentation and/or other materials provided with the distribution.  
// 3. Neither the name of the project nor the names of its contributors
//    may be used to endorse or promote products derived from this software
//    without specific prior written permission. 
// 
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
// ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
// IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
// ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE
// FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
// DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
// OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
// OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF 
// SUCH DAMAGE.
// 

//#include <os.h> 


#include <sys/socket.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <errno.h>
#include <sys/types.h>


#include <stdio.h> 
#include <stdlib.h> 
#include <string.h> 

#define ICMP_ECHO 8 
#define ICMP_ECHOREPLY 0 
 
#define ICMP_MIN 8              // Minimum 8 byte icmp packet (just header) 
 
// 
// ICMP header 
//

struct iphdr 
{ 
  unsigned int h_len:4;          // length of the header 
  unsigned int version:4;        // Version of IP 
  unsigned char tos;             // Type of service 
  unsigned short total_len;      // total length of the packet 
  unsigned short ident;          // unique identifier 
  unsigned short frag_and_flags; // flags 
  unsigned char  ttl;  
  unsigned char proto;           // protocol (TCP, UDP etc) 
  unsigned short checksum;       // IP checksum 
   
  unsigned int source_ip; 
  unsigned int dest_ip; 
};
 
// 
// ICMP header 
//

struct icmphdr 
{ 
  unsigned char i_type; 
  unsigned char i_code; // type sub code
  unsigned short i_cksum; 
  unsigned short i_id; 
  unsigned short i_seq; 
  
  // This is not the std header, but we reserve space for time
  unsigned long timestamp; 
}; 
 
struct pingstat
{
  int tmin;
  int tmax;
  int tsum;
  int ntransmitted;
  int nreceived;
};

#define DEF_PACKET_SIZE 32 
#define MAX_PACKET 1024 
#define PKTSIZ (sizeof(struct icmphdr) + MAX_PACKET)
 

void fill_icmp_data(char *icmp_data, int datasize);
unsigned short checksum(unsigned short *buffer, int size); 
int decode_resp(char *buf, int bytes, struct sockaddr_in *from, struct pingstat *stat);

int cmd_ping(char *ip)
{ 
  int sockraw; 
  struct sockaddr_in dest, from; 
  struct sockaddr_in server_address;
  struct hostent *hp; 
  int bread, datasize, rc; 
  int fromlen = sizeof(from); 
  char *dest_ip; 
  char icmp_data[PKTSIZ]; 
  char recvbuf[PKTSIZ];
  unsigned int addr = 0; 
  unsigned short seq_no = 0; 
  int numpackets;
  char *hostname;
  struct pingstat stat;

  dest.sin_addr.s_addr = inet_addr(ip);
  dest.sin_family = AF_INET;
  dest.sin_port = htons(53);
  int dest_len = sizeof(server_address);
 
  datasize = DEF_PACKET_SIZE; 
 
  if (datasize + sizeof(struct icmphdr) > PKTSIZ)
  { 
    fprintf(stderr, "ping: packet size too large\n"); 
    return 1; 
  } 

  sockraw = socket(AF_INET, SOCK_RAW, IPPROTO_ICMP);   
  if (sockraw < 0) 
  {
    perror("ping: socket");
    return 1; 
  } 
  
  struct timeval timeout;
  timeout.tv_sec = 3;
  
  
  rc = setsockopt(sockraw, SOL_SOCKET, SO_RCVTIMEO,  &timeout, sizeof(timeout)); 
  if (rc < 0) 
  { 
    perror("ping: recv timeout");
    return 1; 
  } 
  
  rc = setsockopt(sockraw, SOL_SOCKET, SO_SNDTIMEO,  &timeout, sizeof(timeout)); 
  if(rc < 0) 
  { 
    perror("ping: send timeout");
    return 1; 
  }

  stat.tmin = 999999999;
  stat.tmax = 0;
  stat.tsum = 0;
  stat.ntransmitted = 0;
  stat.nreceived = 0;

  memset(icmp_data, 0, MAX_PACKET); 
  fill_icmp_data(icmp_data, datasize + sizeof(struct icmphdr));
 
  if (dest.sin_family == AF_INET) 
    printf("PING %s (%s): %d data bytes\n", hostname, inet_ntoa(dest.sin_addr), datasize);

  for (numpackets = 0; numpackets < 5; numpackets++)
  { 
    int bwrote;
    struct icmphdr *icmphdr = (struct icmphdr *) &icmp_data;

    sleep(1);
    icmphdr->i_cksum = 0; 
    icmphdr->timestamp = clock(); 
    icmphdr->i_seq = seq_no++; 
    icmphdr->i_cksum = checksum((unsigned short *) icmp_data, datasize + sizeof(struct icmphdr));
 
    bwrote = sendto(sockraw, icmp_data, datasize + sizeof(struct icmphdr), 0, (struct sockaddr *) &dest, sizeof(dest)); 
    if (bwrote < 0)
    { 
      if (errno == ETIMEDOUT)
      {
        printf("timed out\n"); 
        continue; 
      } 
      perror("ping: sendto"); 
      return 1; 
    }
 
    if (bwrote < datasize)
    {
      fprintf(stdout, "Wrote %d bytes\n", bwrote); 
    }

    fflush(stdout);

    stat.ntransmitted++;

    bread = recvfrom(sockraw ,recvbuf, MAX_PACKET, 0, (struct sockaddr *) &from, &fromlen); 
    if (bread < 0)
    { 
      if (errno == ETIMEDOUT)
      {
        printf("timed out\n"); 
        continue; 
      } 
      perror("ping: recvfrom"); 
      if (numpackets == 4)  
	return 1; 
    }
    else if (decode_resp(recvbuf, bread, &from, &stat) == 0)
      return 0;
    else if (numpackets == 4)
      return 1; 
    sleep(1); 
  } 

  printf("----%s PING Statistics----\n", hostname);
  printf("%d packets transmitted, ", stat.ntransmitted);
  printf("%d packets received, ", stat.nreceived);
  if (stat.ntransmitted)
  {
    if (stat.nreceived > stat.ntransmitted)
      printf("-- somebody's printing up packets!");
    else
      printf("%d%% packet loss", (int) (((stat.ntransmitted - stat.nreceived) * 100) / stat.ntransmitted));
    printf("\n");
  }

  if (stat.nreceived)
  {
    printf("round-trip (ms)  min/avg/max = %d/%d/%d\n", stat.tmin, stat.tsum / stat.nreceived, stat.tmax);
  }

  close(sockraw);
  return 0; 
} 

int decode_resp(char *buf, int bytes, struct sockaddr_in *from, struct pingstat *stat) 
{ 
  struct iphdr *iphdr; 
  struct icmphdr *icmphdr; 
  unsigned short iphdrlen; 
  int triptime;

  iphdr = (struct iphdr *) buf;
  iphdrlen = iphdr->h_len * 4 ; // number of 32-bit words *4 = bytes 

  if (bytes < iphdrlen + ICMP_MIN) 
  { 
    printf("Too few bytes from %s\n", inet_ntoa(from->sin_addr)); 
    return 2;
  } 

  icmphdr = (struct icmphdr *) (buf + iphdrlen); 

  if (icmphdr->i_type != ICMP_ECHOREPLY) 
  { 
    fprintf(stderr, "non-echo type %d recvd\n", icmphdr->i_type); 
    return 1; 
  } 
 //gettid
  if (icmphdr->i_id != (unsigned short) getpid()) 
  { 
    fprintf(stderr, "someone else's packet!\n"); 
    return 3; 
  } 

  triptime = clock() - icmphdr->timestamp;
  stat->tsum += triptime;
  if (triptime < stat->tmin) stat->tmin = triptime;
  if (triptime > stat->tmax) stat->tmax = triptime;
  stat->nreceived++;

  bytes -= iphdrlen + sizeof(struct icmphdr);
  printf("%d bytes from %s:", bytes, inet_ntoa(from->sin_addr)); 
  printf(" icmp_seq=%d", icmphdr->i_seq); 
  printf(" time=%d ms", triptime); 
  printf(" TTL=%d", iphdr->ttl); 
  printf("\n"); 
  return 0;
} 
 
unsigned short checksum(unsigned short *buffer, int size) 
{ 
  unsigned long cksum = 0; 
 
  while (size > 1)
  { 
    cksum += *buffer++; 
    size -= sizeof(unsigned short); 
  } 
   
  if (size) cksum += *(unsigned char *) buffer; 
 
  cksum = (cksum >> 16) + (cksum & 0xffff); 
  cksum += (cksum >> 16); 
  return (unsigned short) (~cksum); 
} 

void fill_icmp_data(char *icmp_data, int datasize)
{ 
  struct icmphdr *icmp_hdr; 
  char *datapart; 
 
  icmp_hdr = (struct icmphdr *) icmp_data; 
 
  icmp_hdr->i_type = ICMP_ECHO; 
  icmp_hdr->i_code = 0; 
  icmp_hdr->i_id = (unsigned short) getpid(); 
  icmp_hdr->i_cksum = 0; 
  icmp_hdr->i_seq = 0; 
   
  datapart = icmp_data + sizeof(struct icmphdr); 
  memset(datapart, 'E', datasize - sizeof(struct icmphdr));  
} 
